{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "eec680f5",
   "metadata": {},
   "source": [
    "# LangGraph 챗봇 구축\n",
    "\n",
    "먼저 `LangGraph`를 사용하여 간단한 챗봇을 만들어 보겠습니다. 이 챗봇은 사용자 메시지에 직접 응답할 것입니다. 비록 간단하지만, `LangGraph`로 구축하는 핵심 개념을 설명할 것입니다. 이 섹션이 끝나면 기본적인 챗봇을 구축하게 될 것입니다.\n",
    "\n",
    "`StateGraph`를 생성하는 것으로 시작하십시오. `StateGraph` 객체는 챗봇의 구조를 \"상태 기계(State Machine)\"로 정의합니다. \n",
    "\n",
    "`nodes`를 추가하여 챗봇이 호출할 수 있는 `llm`과 함수들을 나타내고, `edges`를 추가하여 봇이 이러한 함수들 간에 어떻게 전환해야 하는지를 지정합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "de9d9d8d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# API 키를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API 키 정보 로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6b5c6228",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH17-LangGraph-Modules\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d836a929",
   "metadata": {},
   "source": [
    "## Step-by-Step 개념 이해하기!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c38f326c",
   "metadata": {},
   "source": [
    "### STEP 1. 상태(State) 정의"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2230e22c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated, TypedDict\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from langgraph.graph.message import add_messages\n",
    "\n",
    "\n",
    "class State(TypedDict):\n",
    "    # 메시지 정의(list type 이며 add_messages 함수를 사용하여 메시지를 추가)\n",
    "    messages: Annotated[list, add_messages]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d73ca7b",
   "metadata": {},
   "source": [
    "### STEP 2. 노드(Node) 정의"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbc8fac2",
   "metadata": {},
   "source": [
    "다음으로 \"`chatbot`\" 노드를 추가합니다. \n",
    "\n",
    "노드는 작업의 단위를 나타내며, 일반적으로 정규 **Python** 함수입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "f4db3a07",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# LLM 정의\n",
    "llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n",
    "\n",
    "\n",
    "# 챗봇 함수 정의\n",
    "def chatbot(state: State):\n",
    "    # 메시지 호출 및 반환\n",
    "    return {\"messages\": [llm.invoke(state[\"messages\"])]}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "045f7223",
   "metadata": {},
   "source": [
    "### STEP 3. 그래프(Graph) 정의, 노드 추가"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "09d1d2f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 그래프 생성\n",
    "graph_builder = StateGraph(State)\n",
    "\n",
    "# 노드 이름, 함수 혹은 callable 객체를 인자로 받아 노드를 추가\n",
    "graph_builder.add_node(\"chatbot\", chatbot)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "684fc782",
   "metadata": {},
   "source": [
    "**참고**\n",
    "\n",
    "- `chatbot` 노드 함수는 현재 `State`를 입력으로 받아 \"messages\"라는 키 아래에 업데이트된 `messages` 목록을 포함하는 사전(TypedDict) 을 반환합니다. \n",
    "\n",
    "- `State`의 `add_messages` 함수는 이미 상태에 있는 메시지에 llm의 응답 메시지를 추가합니다. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a18b0aae",
   "metadata": {},
   "source": [
    "### STEP 4. 그래프 엣지(Edge) 추가\n",
    "\n",
    "다음으로, `START` 지점을 추가하세요. `START`는 그래프가 실행될 때마다 **작업을 시작할 위치** 입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ddc4236",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 시작 노드에서 챗봇 노드로의 엣지 추가\n",
    "graph_builder.add_edge(START, \"chatbot\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "000626da",
   "metadata": {},
   "source": [
    "\n",
    "마찬가지로, `END` 지점을 설정하십시오. 이는 그래프 흐름의 종료(끝지점) 를 나타냅니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e3b0a51",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 그래프에 엣지 추가\n",
    "graph_builder.add_edge(\"chatbot\", END)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f5bd367",
   "metadata": {},
   "source": [
    "### STEP 5. 그래프 컴파일(compile)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a599f6f8",
   "metadata": {},
   "source": [
    "마지막으로, 그래프를 실행할 수 있어야 합니다. 이를 위해 그래프 빌더에서 \"`compile()`\"을 호출합니다. 이렇게 하면 상태에서 호출할 수 있는 \"`CompiledGraph`\"가 생성됩니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "4f28795b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 그래프 컴파일\n",
    "graph = graph_builder.compile()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00ce8197",
   "metadata": {},
   "source": [
    "### STEP 6. 그래프 시각화"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4572d38c",
   "metadata": {},
   "source": [
    "이제 그래프를 시각화해봅시다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8235a6d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_teddynote.graphs import visualize_graph\n",
    "\n",
    "# 그래프 시각화\n",
    "visualize_graph(graph)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "afb6eeba",
   "metadata": {},
   "source": [
    "### STEP 7. 그래프 실행"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ffa2cc55",
   "metadata": {},
   "source": [
    "이제 챗봇을 실행해봅시다!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "049fc976",
   "metadata": {},
   "outputs": [],
   "source": [
    "question = \"서울의 유명한 맛집 TOP 10 추천해줘\"\n",
    "\n",
    "# 그래프 이벤트 스트리밍\n",
    "for event in graph.stream({\"messages\": [(\"user\", question)]}):\n",
    "    # 이벤트 값 출력\n",
    "    for value in event.values():\n",
    "        print(\"Assistant:\", value[\"messages\"][-1].content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f82fb67c",
   "metadata": {},
   "source": [
    "자! 여기까지가 가장 기본적인 챗봇 구축이었습니다. \n",
    "\n",
    "아래는 이전 과정을 정리한 전체 코드입니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dec091e3",
   "metadata": {},
   "source": [
    "## 전체 코드"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f3bd4f86",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated, TypedDict\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from langgraph.graph.message import add_messages\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_teddynote.graphs import visualize_graph\n",
    "\n",
    "\n",
    "###### STEP 1. 상태(State) 정의 ######\n",
    "class State(TypedDict):\n",
    "    # 메시지 정의(list type 이며 add_messages 함수를 사용하여 메시지를 추가)\n",
    "    messages: Annotated[list, add_messages]\n",
    "\n",
    "\n",
    "###### STEP 2. 노드(Node) 정의 ######\n",
    "# LLM 정의\n",
    "llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n",
    "\n",
    "\n",
    "# 챗봇 함수 정의\n",
    "def chatbot(state: State):\n",
    "    # 메시지 호출 및 반환\n",
    "    return {\"messages\": [llm.invoke(state[\"messages\"])]}\n",
    "\n",
    "\n",
    "###### STEP 3. 그래프(Graph) 정의, 노드 추가 ######\n",
    "# 그래프 생성\n",
    "graph_builder = StateGraph(State)\n",
    "\n",
    "# 노드 이름, 함수 혹은 callable 객체를 인자로 받아 노드를 추가\n",
    "graph_builder.add_node(\"chatbot\", chatbot)\n",
    "\n",
    "###### STEP 4. 그래프 엣지(Edge) 추가 ######\n",
    "# 시작 노드에서 챗봇 노드로의 엣지 추가\n",
    "graph_builder.add_edge(START, \"chatbot\")\n",
    "\n",
    "# 그래프에 엣지 추가\n",
    "graph_builder.add_edge(\"chatbot\", END)\n",
    "\n",
    "###### STEP 5. 그래프 컴파일(compile) ######\n",
    "# 그래프 컴파일\n",
    "graph = graph_builder.compile()\n",
    "\n",
    "###### STEP 6. 그래프 시각화 ######\n",
    "# 그래프 시각화\n",
    "visualize_graph(graph)\n",
    "\n",
    "###### STEP 7. 그래프 실행 ######\n",
    "question = \"서울의 유명한 맛집 TOP 10 추천해줘\"\n",
    "\n",
    "# 그래프 이벤트 스트리밍\n",
    "for event in graph.stream({\"messages\": [(\"user\", question)]}):\n",
    "    # 이벤트 값 출력\n",
    "    for value in event.values():\n",
    "        print(value[\"messages\"][-1].content)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
